package cn.bubi.sdk.starter;

import cn.bubi.access.adaptation.blockchain.bc.RpcService;
import cn.bubi.access.adaptation.blockchain.bc.response.Account;
import cn.bubi.access.adaptation.blockchain.bc.response.TransactionHistory;
import cn.bubi.access.adaptation.blockchain.bc.response.ledger.Ledger;
import cn.bubi.access.adaptation.blockchain.bc.response.operation.SetMetadata;
import cn.bubi.access.starter.BlockchainProperties;
import cn.bubi.access.utils.blockchain.BlockchainKeyPair;
import cn.bubi.access.utils.blockchain.SecureKeyGenerator;
import cn.bubi.sdk.core.exception.SdkException;
import cn.bubi.sdk.core.operation.OperationFactory;
import cn.bubi.sdk.core.operation.impl.CreateAccountOperation;
import cn.bubi.sdk.core.spi.BcOperationService;
import cn.bubi.sdk.core.spi.BcQueryService;
import cn.bubi.sdk.core.transaction.Transaction;
import cn.bubi.sdk.core.transaction.model.TransactionCommittedResult;
import cn.bubi.sdk.core.utils.GsonUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * Created By 张猛 On 2018-5-23 10:10
 */

@Component
public class BlockChain {
    private Logger log = LoggerFactory.getLogger(BlockChain.class);
    @Autowired
    private BcOperationService operationService;
    @Autowired
    private BcQueryService bcQueryService;
    @Autowired
    private RpcService rpcService;
    @Autowired
    private BlockchainProperties props;

    //获取区块情况
    public F.Either<Ledger, ErrorCode> getLegder() {
        Ledger ledger = rpcService.getLedger();
        return F.Either.Left(ledger);
    }

    //获取区块情况
    public F.Either<Ledger, ErrorCode> getLegder(long seq) {
        Ledger ledger = rpcService.getLedgerBySeq(seq);
        return F.Either.Left(ledger);
    }

    /***
     * 创建账号
     * @param metadatas 账号备注信息
     * @return
     */
    public F.Either<AccountWithKeyPair, ErrorCode> createAccount(List<SetMetadata> metadatas) {
        BlockchainProperties.SponsorAccountPoolConfig accountPool = props.getAccountPool();
        Transaction transaction = operationService.newTransaction(accountPool.getAddress());
        BlockchainKeyPair keyPair = SecureKeyGenerator.generateBubiKeyPair();
        try {
            CreateAccountOperation.Builder builder = new CreateAccountOperation.Builder()
                    .buildDestAddress(keyPair.getBubiAddress());
            for(SetMetadata single : metadatas) {
                builder.buildAddMetadata(single.getKey(), single.getValue());
            }
            CreateAccountOperation createAccountOperation = builder.build();
            TransactionCommittedResult result = transaction.buildAddOperation(createAccountOperation).buildAddSigner(accountPool.getPublicKey(), accountPool.getPrivateKey()).commit();
            log.info("transaction commit result: {}", GsonUtil.toJson(result));
            Account account = bcQueryService.getAccount(keyPair.getBubiAddress());
            AccountWithKeyPair accountWithKeyPair = new AccountWithKeyPair();
            accountWithKeyPair.setAccount(account);
            accountWithKeyPair.setKeyPair(keyPair);
            accountWithKeyPair.setTransaction(result);
            return F.Either.Left(accountWithKeyPair);
        } catch (SdkException e) {
            log.error("创建用户失败", e);
            return F.Either.Right(ErrorCode.ACCOUNT_CREATE_ERROR);
        }
    }

    /***
     * 创建账号 with 合约
     * @param metadatas 元数据
     * @param script 合约
     * @return
     */
    public F.Either<AccountWithKeyPair, ErrorCode> createAccount(List<SetMetadata> metadatas, String script) {
        BlockchainProperties.SponsorAccountPoolConfig accountPool = props.getAccountPool();
        Transaction transaction = operationService.newTransaction(accountPool.getAddress());
        BlockchainKeyPair keyPair = SecureKeyGenerator.generateBubiKeyPair();
        try {
            CreateAccountOperation.Builder builder = new CreateAccountOperation.Builder()
                    .buildDestAddress(keyPair.getBubiAddress());
            for(SetMetadata single : metadatas) {
                builder.buildAddMetadata(single.getKey(), single.getValue());
            }
            if(StringUtils.isNotBlank(script)) {
                builder.buildScript(script);
            }
            CreateAccountOperation createAccountOperation = builder.build();
            TransactionCommittedResult result = transaction.buildAddOperation(createAccountOperation).buildAddSigner(accountPool.getPublicKey(), accountPool.getPrivateKey()).commit();
            log.info("transaction commit result: {}", GsonUtil.toJson(result));
            Account account = bcQueryService.getAccount(keyPair.getBubiAddress());
            AccountWithKeyPair accountWithKeyPair = new AccountWithKeyPair();
            accountWithKeyPair.setAccount(account);
            accountWithKeyPair.setKeyPair(keyPair);
            accountWithKeyPair.setTransaction(result);
            return F.Either.Left(accountWithKeyPair);
        } catch (SdkException e) {
            log.error("创建用户失败", e);
            return F.Either.Right(ErrorCode.ACCOUNT_CREATE_ERROR);
        }
    }

    /***
     * 根据地址查询账号明细
     * @param address 账号地址
     * @return
     */
    public F.Either<Account, ErrorCode> getAccount(String address) {
        Account account = bcQueryService.getAccount(address);
        if(account == null) {
            return F.Either.Right(ErrorCode.ACCOUNT_NOTFOUND);
        }
        return F.Either.Left(account);
    }

    // 根据地址和 meta key 获取元数据
    public F.Either<SetMetadata, ErrorCode> getMetaData(String address, String key) {
        SetMetadata metadata = bcQueryService.getAccount(address, key);
        if(metadata == null) {
            return F.Either.Right(ErrorCode.METADATA_NOTFOUND);
        }
        return F.Either.Left(metadata);
    }

    //发行资产
    public F.Either<AccountWithTransaction, ErrorCode> publicAsset(BlockchainKeyPair blockchainKeyPair, Assetx assetx) {
        String assetCode = assetx.getAssetCode();
        long amount = assetx.getAmount();
        Transaction issueTransaction = operationService.newTransaction(blockchainKeyPair.getBubiAddress());

        try {
            TransactionCommittedResult result = issueTransaction
                    .buildAddOperation(OperationFactory.newIssueAssetOperation(assetCode, amount))
                    .buildAddSigner(blockchainKeyPair.getPubKey(), blockchainKeyPair.getPriKey())
                    .commit();
            log.info("发行资产 commit result: {}", GsonUtil.toJson(result));
            Account account = bcQueryService.getAccount(blockchainKeyPair.getBubiAddress());
            return F.Either.Left(new AccountWithTransaction(account, result));
        } catch (SdkException e) {
            log.error("发行资产失败", e);
            return F.Either.Right(ErrorCode.ASSET_ISSUE_ERROR);
        }
    }

    /***
     * 转移资产
     * @param source 转让方相关信息
     * @param destAddr  受让方相关信息
     * @return
     */
    public F.Either<AccountWithTransaction, ErrorCode> transferAsset(BlockchainKeyPair source, String destAddr, Assetx assetx) {
        String assetCode = assetx.getAssetCode();
        long amount = assetx.getAmount();
        Transaction transaction = operationService.newTransaction(source.getBubiAddress());
        try {
            TransactionCommittedResult commit = transaction
                    .buildAddOperation(OperationFactory.newPaymentOperation(destAddr, assetx.getIssuer(), assetCode, amount))
                    .buildAddSigner(source.getPubKey(), source.getPriKey())
                    .commit();
            Account account = bcQueryService.getAccount(destAddr);
            return F.Either.Left(new AccountWithTransaction(account, commit));
        } catch (Exception e) {
            log.error("转移资产失败", e);
            return F.Either.Right(ErrorCode.ASSET_TRANSFER_ERROR);
        }
    }

    /***
     * 修改账号元数据
     * @param source 账号源
     * @return
     */
    public F.Either<AccountWithTransaction, ErrorCode> updateMetadata(BlockchainKeyPair source, SetMetadata metadata) {
        String key = metadata.getKey();
        SetMetadata oldMetadata = bcQueryService.getAccount(source.getBubiAddress(), key);
        Transaction transaction = operationService.newTransaction(source.getBubiAddress());
        try {
            if(oldMetadata != null) {
                oldMetadata.setValue(metadata.getValue());
                transaction.buildAddOperation(OperationFactory.newUpdateSetMetadataOperation(oldMetadata));
            } else {
                transaction.buildAddOperation(OperationFactory.newSetMetadataOperation(metadata.getKey(), metadata.getValue()));
            }
            TransactionCommittedResult commit = transaction.buildAddSigner(source.getPubKey(), source.getPriKey()).commit();
            Account account = bcQueryService.getAccount(source.getBubiAddress());
            return F.Either.Left(new AccountWithTransaction(account, commit));
        } catch (SdkException e) {
            log.error("修改元数据失败", e);
            return F.Either.Right(ErrorCode.METADATA_UPDATE_ERROR);
        }
    }



    /***
     * 根据hash 进行交易查询
     * @param hash
     * @return
     */
    public F.Either<TransactionHistory, ErrorCode> getTransactionByHash(String hash) {
        TransactionHistory transactionHistoryByHash = rpcService.getTransactionHistoryByHash(hash);
        if(transactionHistoryByHash == null || transactionHistoryByHash.getTotalCount() <= 0) {
            return F.Either.Right(ErrorCode.TRANSACTION_NOT_FOUND);
        }
        return F.Either.Left(transactionHistoryByHash);
    }

    /***
     *
     * @param seqId 区块数
     * @param start 交易起
     * @param limit 交易限制条数
     * @return
     */
    public F.Either<TransactionHistory, ErrorCode> getTransactionBySeqId(Long seqId, int start, int limit) {
        TransactionHistory transactionHistoryBySeq = rpcService.getTransactionHistoryBySeq(seqId, 0, 10);
        if(transactionHistoryBySeq.getTotalCount() <= 0) {
            return F.Either.Right(ErrorCode.TRANSACTION_NOT_FOUND);
        }
        return F.Either.Left(transactionHistoryBySeq);
    }

}
